iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 16
1

前言

雖然我們還有Zookeeper的ZAB共識演算法還沒看,
但是RPC算是很基礎的分散式系統溝通方法,在Raft裡面也是直接使用並將Spec寫在論文中,因此我們先來看一下分散式系統怎麼溝通的吧。

RPC

其實老實說我是碩班讀Paper才第一次看到這個名詞,也才知道原來有這樣的一種方法,不知道為什麼我大學從沒聽過這個,可能是我漏聽了吧(?

一般來說在寫code的時候,一般都會用functions或是Class來讓程式模組化提供「功能」。而這些functions與物件通常都是在自己機器上執行,甚至可以說這些功能在程式被編譯成Binary code或是開始執行後一般來說是不會提供給別的程式甚是另一台機器上的程式使用的。

而Remote Procedure Call(RPC)便是將本地程式的function或是物件裡的method,外顯出來,利用TCP/UDP/HTTP各種網路通訊方法,讓別台機器上面運行的程式可以透過網路呼叫你的這個function或是method。

RPC的目的:

  • 提供一種分散式系統不同Components溝通的方法。
  • 可以隱藏通訊的方法,對Client來說就是呼叫function。

架構流程

正如上面的敘述,RPC簡單說就是一台機器上面的程式將自己的function expose出來,透過網路介面接受另外一台機器上面的程式呼叫。

因此我們稱提供functions的為server,呼叫的為client

步驟也非常直觀,

  1. Client就像是呼叫本地的function一樣直接使用該function
  2. 但是其實該function並不是真的,他底部會先將使用者的一些metadata與參數做encoding的動作,可以是JSON/XML/binary stream...等
  3. 接著再透過網路介面TCP/UDP/HTTP將訊息傳給真正提供function的Server
  4. Server接收到後將訊息decoding
  5. Server真正的呼叫真實的function,並得到return的結果
  6. Server將結果encoding後再將結果送回Client
  7. Client decoding後得到結果

此方法將function抽象出來,對Client來說他以為他是呼叫本地function(寫code時也是一樣的感覺),然後得到return值
但是其實底層是將參數傳到另外一台機器上並使用那一台機器的function得到結果回傳。

錯誤處理

但是回到分散式系統的本質,哈哈沒錯,就是網路一定會出包!
像這樣有經過網路的設計或是協定一定是要考量錯誤處理的部份。

除此之外另一件事情就是如同使用本地的function一樣,function的呼叫可能是synchronous的,因此如果處理到一半另外一邊斷線了,怎麼辦呢?
(一路看到這裡應該有底了: 斷線的101種解法...Timeout xD)

我們整理一下可能的錯誤:

  1. 封包丟失
  2. 網路斷線
  3. Server處理過慢
  4. Server可能crash

從Client角度來說,他甚至沒辦法區分上面的錯誤,也不知道發生什麼事。

Best Effort (At Least once)

第一種方法就是Client呼叫後如果超過某個時間沒收到回覆,就再retry一次。
並設定retry次數,如果超過次數就返回失敗。

問題:

  1. 可能Client -> Server的通訊是正常的,但是回來的通訊出問題,因此Server每次回傳結果都失敗 => 浪費Server資源

  2. 如果此RPC操做並不是idempotent,這個比較嚴重。比方說此RPC code是對一個k-v store server呼叫set:

    1. client call set(v, 10),封包延遲,server沒收到
    2. client 再一次 call set(v, 10),server回傳succeed
    3. client call set(v, 20),server回傳succeed
    4. 此時第一次set(v, 10)訊息傳到了,server回傳succeed
    5. 這時get(v)會得到v=10的訊息而不是最新的20

Best Effort的要求是function為idempotent,不改變狀態,類似functional programming那種概念或是Amazon Lambda function,才會採用Best Effort。

At Most Once

如果我們重新看一次上面錯誤的範例,其實除了function為idempotent的方式外,還有另外一種解決方法。

那就是讓Server判斷此次呼叫是否跟之前的一樣,也就是Server可以判斷是否是重複呼叫,如果是,就直接返回暫存的結果,而不重新執行function。

也因此Client的呼叫可以帶上ID,也就是跟參數一起被encoding的metadata,例如: (Client IP, Timestamp)

因為同一個呼叫不會被重複呼叫兩次,所以稱為At Most Once

但是這會產生兩個問題:

  1. 什麼時候可以刪除掉暫存的訊息?
    Server如果必須暫存所有的結果這是很佔空間的,尤其分散式系統有很多Clients會呼叫Server的function。

    • 解法1: Client的呼叫同時加上上一次RPC成功的ID,讓Server知道該次的結果有正確送達,可以刪除掉暫存的資料。
    • 解法2: Client的ID是一個遞增的序列,如果有成功收到Server回傳的結果,則ID+1,Server即知道上一次的RPC成功。
  2. 如果上一個RPC Server還沒處理完,Client已經Retry了怎麼辦?

    • 針對RPC Call的ID維護一個flag,如果還沒處理完顯示Pending,因此同一個ID的請求又過來時,可以等待或是忽略
  3. 如果Server 失效重啟怎麼辦?

    • 這個問題在於如果暫存資料是在記憶體,則會消失不見
    • 一種方法是將暫存資料也記在disk中
    • 另外一種比方說分散式儲存就可以用replicate date存在replica server (陰魂不散的Consistency問題又來囉...)

Golang RPC Lib實作

Golang的RPC為 "At-Most-Once"
步驟:

  1. 使用TCP連線
  2. 透過TCP 寫入請求
  3. 永遠不會重新retry同一個請求 => Server永遠不會看到重複的請求
  4. 對Client來說,如果沒收到回覆則回報錯誤
    • 可能超時(TCP超時機制)
    • 可能Server沒收到請求
    • 可能Server回傳的結果沒傳到

範例

Server

package main

import (
	"log"
	"net"
	"net/rpc"
)

// 作為rpc的server func,必須接受兩個參數 (duck typing) (request string, reply *string)
// 另外返回值為error,必須為公開的方法

type HelloService struct{}

func (h *HelloService) Hello(request string, reply *string) error {
	*reply = "hello: " + request
	return nil
}

func main() {
	// rpc.Register會將object中所有滿足rpc規則的方法註冊為rpc函數
	// 所有註冊的方法會被放在HelloService服務空間下。
	rpc.RegisterName("HelloService", new(HelloService))

	// 建立聆聽一個tcp連線,
	listner, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listner.Accept()
		if err != nil {
			log.Fatal(err)
		}

		// rpc.ServeConn在該連線上提供client rpc函數
		go rpc.ServeConn(conn)
	}
}

Client

package main

import (
	"fmt"
	"log"
	"net/rpc"
)

func main() {
	// rpc.Dial 連接該rpc服務
	conn, err := rpc.Dial("tcp", ":8080")
	if err != nil {
		log.Fatal(err)
	}

	var reply string
	// 透過conn.Call來使用rpc方法。
	// conn.Call(serviceMethod, args, reply)
	conn.Call("HelloService.Hello", "Jack", &reply)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply)
}

總結

以上就是關於RPC的簡介啦

Ref:


上一篇
Day 15 - 共識演算法 - 先喘口氣做一個複習
下一篇
Day 17 - 共識演算法之最後一個了 - Zookeeper的ZAB
系列文
分散式系統 - 在分散的世界中保持一致30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言